// ============================================================================
//  gDestroyerBeamInfo.uc ::
// ============================================================================
class gDestroyerBeamInfo extends Actor
    dependson(gDestroyerNode);



struct SBeamFragment
{
    var() array<vector> Points;
};


var() float Speed;
var() float LifeDist;
var() byte Tesselation;
var() float ControlDistNode;
var() float ControlDistAnchor;
var() vector CollisionExtent;
var() float FixedTickDelta;

var() bool bDrawBeam;
var() bool bDrawDebug;
var() bool bDrawDebugNodes;
var() bool bDrawDebugNodeCross;
var() bool bDrawDebugBeam;

var() color ColorTruncate;
var() color ColorNode;
var() color ColorNode2;
var() color ColorSegment;
var() color ColorSegment2;

var() float DebugCrossSize;


var gDestroyerNode NodeFirst;       // for forward iteration
var gDestroyerNode NodeLast;        // for reverse iteration
var gDestroyerNode NodeCollision;   // Valid during collision update only

var bool bReleased;
var float NodeLifespan;
var float FixedTickAccum;

var array<gDestroyerBeamEmitter> BeamEmitters;
var array<gDestroyerDispEmitter> DispEmitters;
var array<SBeamFragment> BeamFragments;

var vector OldFireLoc;
var rotator OldFireRot;

var gDestroyerScout Scout;

var() class<Projector> ScorchClass;
var() float MinScorchDist;
var vector OldScorchLoc;

var() class<Emitter> HitEffectClass;
var vector OldHitLoc;
var() float MinHitDist;

var() class<gDestroyerBeamEmitter> BeamEmitterClass;
var() class<gDestroyerDispEmitter> DispEmitterClass;

var() class<Emitter> FlashEffectClass;
var   Emitter FlashEffect;

/*  TODO
    - beam emitter bounding box?
    - anchor node visual glitch?
    - update anchor node every tick?
    - smooth truncation?
*/

replication
{
    reliable if( Role == ROLE_Authority )
        bReleased;
}


simulated event PreBeginPlay()
{
}

simulated event PostBeginPlay()
{
    //gLog( "PostBeginPlay" );

    Super.PostBeginPlay();

    Scout = Spawn(class'gDestroyerScout');

    if( Role == ROLE_Authority )
    {
        if( Instigator != None && gDestroyer(Instigator.Weapon) != None )
        {
            gDestroyerFireAlt(gDestroyer(Instigator.Weapon).GetFireMode(1)).GetFireCoords(OldFireLoc, OldFireRot);
            SetRotation(OldFireRot);
        }
    }
    else
    {
        OldFireLoc = Location;
        OldFireRot = Rotation;
    }

    // init constants
    NodeLifespan = LifeDist / Speed;
    FixedTickAccum = FixedTickDelta;

    // create first node
    AddAnchorNode();
}

simulated event PostNetBeginPlay()
{
    if( Role != ROLE_Authority )
    {
        OldFireLoc = Location;
        OldFireRot = Rotation;
    }
}

simulated event Destroyed()
{
    local gDestroyerNode Node,NodeNext;
    local int i;

    Super.Destroyed();

    // free nodes
    for( Node=NodeFirst; Node!=None; Node=NodeNext )
    {
        NodeNext = Node.Next;
        class'gDestroyerNodePool'.static.ReleaseNode(Node);
    }
    NodeFirst = None;
    NodeLast = None;

    // free visuals
    for( i=0; i<BeamEmitters.Length; ++i )
    {
        BeamEmitters[i].Destroy();
    }
    BeamEmitters.Length = 0;

    for( i=0; i<DispEmitters.Length; ++i )
    {
        DispEmitters[i].Destroy();
    }
    DispEmitters.Length = 0;

    if( Scout != None )
        Scout.Destroy();
}

final simulated function Release()
{
    //gLog( "Release" );

    assert(NodeFirst != None);
    assert(NodeFirst.bAnchor);
    //assert(!bReleased);

    bReleased = True;
    NetUpdateTime = Level.TimeSeconds - 1;

    if( FlashEffect != None )
    {
        FlashEffect.Kill();
        FlashEffect = None;
    }

    // make anchor node normal if there's another node
    if( NodeFirst.Next != None )
    {
        NodeFirst.bAnchor = False;
        NodeFirst.Age = NodeFirst.Next.Age - FixedTickDelta;
        NodeFirst.Dir = NodeFirst.Next.Dir;
        NodeFirst.StartLocation = NodeFirst.Next.StartLocation;

        Lifespan = 5; // hack! make it die with last node instead
    }
    // otherwise destroy
    else
    {
        Destroy();
    }
}

final simulated function AddAnchorNode()
{
    assert(NodeFirst == None);
    assert(NodeLast == None);

    NodeFirst = class'gDestroyerNodePool'.static.GetNode();
    NodeFirst.bAnchor = True;
    NodeFirst.bTruncate = False;
    NodeLast = NodeFirst;
    NodeFirst.Next = None;
    NodeFirst.Prev = None;
}


final simulated function gDestroyerNode AddNode( vector nLocation, vector nDir )
{
    local gDestroyerNode Node;

    //gLog( "AddNode" );

    // Init
    Node = class'gDestroyerNodePool'.static.GetNode();
    Node.bTruncate = False;
    Node.bAnchor = False;
    Node.Location = nLocation;
    Node.Dir = nDir;
    Node.Age = 0.0;
    Node.StartLocation = nLocation;

    // if nodes found, prepend node
    if( NodeFirst != None )
    {
        // if anchor present, add as second
        if( NodeFirst.bAnchor )
        {
            Node.ForceCollision = NodeFirst.ForceCollision;

            // if at least two nodes present
            if( NodeFirst.Next != None )
            {
                Node.Next = NodeFirst.Next;
                Node.Prev = NodeFirst;
                NodeFirst.Next.Prev = Node;
                NodeFirst.Next = Node;
            }
            // if only anchor node present
            else
            {
                assert(NodeLast == NodeFirst);
                Node.Next = NodeFirst.Next;
                Node.Prev = NodeFirst;
                NodeFirst.Next = Node;
                NodeLast = Node;
            }
        }
        // if anchor not present, add as first
        else
        {
            Node.Next = NodeFirst;
            Node.Prev = None;
            NodeFirst.Prev = Node;
            NodeFirst = Node;
        }
    }
    // if nodes not found, assert
    // beam should have been destroyed when last node was released
    else
    {
        //gLog( "AddNode NO BEAM" );
        assert(False);
    }

    return Node;
}

simulated event Tick( float DeltaTime )
{
    DoFixedTick(DeltaTime);
}

final simulated function DoFixedTick( float DeltaTime )
{
    local bool bFire;
    local gDestroyerNode Node;
    local Quat NewQuat, OldQuat;
    local vector FireLoc, V;
    local rotator FireRot, R;
    local float Alpha, LocalAccum;
    local int SubTick;


    if( Role == ROLE_Authority )
    {
        if( !bReleased && Instigator != None && gDestroyer(Instigator.Weapon) != None )
        {
            gDestroyerFireAlt(gDestroyer(Instigator.Weapon).GetFireMode(1)).GetFireCoords(FireLoc, FireRot);
            bFire = True;

            SetRotation(FireRot);
            SetLocation(FireLoc);

            assert(FireLoc == Location);
            assert(FireRot == Rotation);

            NetUpdateTime = Level.TimeSeconds - 1;
        }
    }
    else
    {
        bFire = !bReleased;
        if( bReleased && NodeFirst != None && NodeFirst.bAnchor )
        {
            Release();
        }
    }

    FixedTickAccum += DeltaTime;
    LocalAccum = FixedTickAccum;
    if( FixedTickAccum >= FixedTickDelta )
    {
        if( bFire )
        {
            OldQuat = QuatFromRotator(OldFireRot);
            NewQuat = QuatFromRotator(Rotation);
            AlignQuatWith(OldQuat,NewQuat);
        }

        while( FixedTickAccum >= FixedTickDelta )
        {
            FixedTickAccum -= FixedTickDelta;
            ++SubTick;

            if( bFire )
            {
                Alpha = 1.0 - (FixedTickAccum / LocalAccum);
                V = OldFireLoc + Normal(Location-OldFireLoc) * VSize(Location-OldFireLoc) * Alpha;
                R = QuatToRotator( QuatSlerp( OldQuat, NewQuat, Alpha ) );

                R = rotator(vector(R) + 0.01 * VRand());

                Node = AddNode(V,vector(R));
            }


            // Update node age
            DoAging(FixedTickDelta);
            if( NodeFirst == None )
                return;

            // Update node location & collision
            DoPhysics(FixedTickDelta);
            if( NodeFirst == None )
                return;

            // Update control points
            DoControlPoints(FixedTickDelta);

            // Create beam fragments
            DoFragments();
        }

        if( bFire )
        {
            OldFireLoc = V;
            OldFireRot = R;
        }

        if( NodeFirst == None )
            return;

        if( Level.NetMode != NM_DedicatedServer )
        {
            if( bDrawBeam )
            {
                DoEmitterSpawning();
            }

            if( bDrawBeam )
            {
                DrawBeam();
            }
        }
    }
}


// so the angle between them is <= 180 degrees
final simulated function AlignQuatWith( Quat BaseQuat, out Quat OutQuat )
{
    local float Minus, Plus;

    Minus = Square(OutQuat.X-BaseQuat.X) + Square(OutQuat.Y-BaseQuat.Y) + Square(OutQuat.Z-BaseQuat.Z) + Square(OutQuat.W-BaseQuat.W);
    Plus  = Square(OutQuat.X+BaseQuat.X) + Square(OutQuat.Y+BaseQuat.Y) + Square(OutQuat.Z+BaseQuat.Z) + Square(OutQuat.W+BaseQuat.W);

    if( Minus > Plus )
    {
        OutQuat.X = -OutQuat.X;
        OutQuat.Y = -OutQuat.Y;
        OutQuat.Z = -OutQuat.Z;
        OutQuat.W = -OutQuat.W;
    }
}

final simulated function DoAging( float DeltaTime)
{
    local gDestroyerNode Node, NextNode, PrevNode;

    if( NodeLast == None )
        return;

    // release obsolete nodes, except for first one
    Node = NodeLast;
    while( Node != None && !Node.bAnchor )
    {
        NextNode = Node.Next;
        PrevNode = Node.Prev;

        Node.Age += DeltaTime;

        // release if age exceeded
        if( Node.Age > NodeLifespan )
        {
            if( Node != NodeLast ){assert(False); }

            if( PrevNode == None )
            {
                NodeLast = None;
                NodeFirst = None;

                class'gDestroyerNodePool'.static.ReleaseNode(Node);
                Destroy();
                return;
            }
            else //if( PrevNode.Age > NodeLifespan && !PrevNode.bAnchor )
            {
                PrevNode.Next = None;
                NodeLast = PrevNode;

                class'gDestroyerNodePool'.static.ReleaseNode(Node);
            }
//            else
//            {
//                // truncate
//            }
        }

        Node = PrevNode;
    }
}

final simulated function DoPhysics( float DeltaTime)
{
    local gDestroyerNode Node, NextNode, PrevNode;

    Node = NodeFirst;
    if( NodeFirst == None )
        return;

    if( Node.bAnchor )
        Node = Node.Next;

    // release obsolete nodes, except for first one
    while( Node != None )
    {
        NextNode = Node.Next;
        PrevNode = Node.Prev;

        // TODO: check collision on movement
        Node.Location = Node.StartLocation + Node.Dir * Speed * Node.Age;

        Node = NextNode;
    }


    // anchor node
    if( NodeFirst.bAnchor )
    {
        NodeFirst.Dir = vector(Rotation);

        if( Pawn(Owner).IsFirstPerson() && Pawn(Owner).Weapon != None )
        {
            NodeFirst.Location = Pawn(Owner).Weapon.GetBoneCoords('Muzzle').Origin;
        }
        else if( xPawn(Owner).WeaponAttachment != None )
        {
            NodeFirst.Location = xPawn(Owner).WeaponAttachment.GetBoneCoords('Muzzle').Origin;
        }
        else
        {
            NodeFirst.Location = Pawn(Owner).Location;
        }
    }

    DoBeamPoints();
    DoCollision(DeltaTime);
    DoOldBeamPoints();
}


final simulated function DoCollision( float DeltaTime )
{
    local gDestroyerNode Node, NextNode, PrevNode;
    local vector HL, HN;
    local int i;
    local Actor A;
    local gDestroyerNode.EBeamCollision NewCollision, LastCollision;

    NodeCollision = None;
    Scout.SetLocation(NodeFirst.Location);
    Scout.SetCollision(True,False);

    LastCollision = BC_None;

    Node = NodeFirst;
    while( Node != None )
    {
        NextNode = Node.Next;
        PrevNode = Node.Prev;
        NodeCollision = Node;

        Node.BeamCollision.Length = Node.BeamPoints.Length;
        Node.BeamSplits.Length = Node.BeamPoints.Length;
        Node.BeamVisibility.Length = Node.BeamPoints.Length;

        if( Node.bAnchor )
            Node.ForceCollision = BC_None;

        for( i=0; i<Node.BeamPoints.Length; ++i )
        {
            if( Node.ForceCollision != BC_None )
            {
                NewCollision = Node.ForceCollision;
                Node.BeamCollision[i] = NewCollision;
            }
            else if( Node.BeamCollision[i] == BC_None || Node.bAnchor )
            {
                Scout.SetLocation(Node.BeamPoints[i]);
                assert( Scout.Location == Node.BeamPoints[i] );

                if( Scout.Region.ZoneNumber == 0 )
                {
                    NewCollision = BC_World;

                    if( Node.OldBeamPoints.Length == Node.BeamPoints.Length )
                        A = Scout.Trace(HL, HN, Node.BeamPoints[i], Node.OldBeamPoints[i], False, CollisionExtent);
                    else
                        A = Scout.Trace(HL, HN, Node.BeamPoints[i], Node.BeamPoints[i]-Node.Dir*128.0, False, CollisionExtent);

                    if( A != None && A.bWorldGeometry && VSize(HL-OldScorchLoc) > MinScorchDist )
                    {
                        Spawn(HitEffectClass,,,HL,rotator(HN));
                        Spawn(ScorchClass,,,HL-Node.Dir*4.0,rotator(Node.Dir));
                        OldScorchLoc = HL;
                    }
                }
                else
                {
                    NewCollision = BC_None;
                }

                foreach Scout.TouchingActors(class'Actor',A)
                {
                    if( A != Instigator )
                    {
                        if( A.bCanBeDamaged )
                        {
                            if( VSize(HL-OldHitLoc) > MinHitDist )
                            {
                                Spawn(HitEffectClass,,,HL,rotator(HN));
                                OldHitLoc = HL;
                            }

                            if( Role == ROLE_Authority )
                            {
                                DoFireDamage(A);
                            }
                            NewCollision = BC_Actor;
                        }
                        else if( A.bWorldGeometry )
                        {
                            if( VSize(HL-OldHitLoc) > MinHitDist )
                            {
                                Spawn(HitEffectClass,,,HL,rotator(HN));
                                OldHitLoc = HL;
                            }
                            NewCollision = BC_World;

                            if( VSize(HL-OldScorchLoc) > MinScorchDist )
                            {
                                Spawn(ScorchClass,,,HL-Node.Dir*4.0,rotator(Node.Dir));
                                OldScorchLoc = HL;
                            }
                        }
                        else if( A.bBlockActors )
                        {
                            if( VSize(HL-OldHitLoc) > MinHitDist )
                            {
                                Spawn(HitEffectClass,,,HL,rotator(HN));
                                OldHitLoc = HL;
                            }
                            NewCollision = BC_Actor;
                        }
                    }
                }

                if( NewCollision == BC_None && Node.OldBeamPoints.Length == Node.BeamPoints.Length )
                {
                    A = Scout.Trace(HL, HN, Node.BeamPoints[i], Node.OldBeamPoints[i], True, CollisionExtent);
                    if( A != None )
                    {
                        if( A != Instigator )
                        {
                            if( A.bCanBeDamaged )
                            {
                                if( VSize(HL-OldHitLoc) > MinHitDist )
                                {
                                    Spawn(HitEffectClass,,,HL,rotator(HN));
                                    OldHitLoc = HL;
                                }

                                if( Role == ROLE_Authority )
                                {
                                    DoFireDamage(A);
                                }
                                NewCollision = BC_Actor;
                            }
                            else if( A.bWorldGeometry )
                            {
                                if( VSize(HL-OldHitLoc) > MinHitDist )
                                {
                                    Spawn(HitEffectClass,,,HL,rotator(HN));
                                    OldHitLoc = HL;
                                }
                                NewCollision = BC_World;

                                if( VSize(HL-OldScorchLoc) > MinScorchDist )
                                {
                                    Spawn(ScorchClass,,,HL-Node.Dir*4.0,rotator(Node.Dir));
                                    OldScorchLoc = HL;
                                }
                            }
                            else if( A.bBlockActors )
                            {
                                if( VSize(HL-OldHitLoc) > MinHitDist )
                                {
                                    Spawn(HitEffectClass,,,HL,rotator(HN));
                                    OldHitLoc = HL;
                                }
                                NewCollision = BC_Actor;
                            }
                        }
                    }
                }

                if( NewCollision != BC_None || Node.bAnchor )
                {
                    Node.BeamCollision[i] = NewCollision;
                }
            }
            else
            {
                NewCollision = Node.BeamCollision[i];
            }

            // visibility
            if( NewCollision != BC_None )
                Node.BeamVisibility[i] = BV_Hide;
            else
                Node.BeamVisibility[i] = BV_Show;

            // anchor collision
            if( (Node.bAnchor || Node.CollisionTicks == 0) && NewCollision != BC_None )
                Node.ForceCollision = NewCollision;

            // splits
            if( LastCollision == BC_None )
            {
                if( NewCollision != BC_None )
                {
                    Node.BeamSplits[i] = BS_Begin;
                }
                else
                {
                }
            }
            else
            {
                if( NewCollision != BC_None )
                {
                    Node.BeamSplits[i] = BS_Split;
                }
                else
                {
                    Node.BeamSplits[i] = BS_End;
                }
            }

            LastCollision = NewCollision;
        }

        ++Node.CollisionTicks;
        Node = NextNode;
    }


    NodeCollision = None;
    Scout.SetCollision(False,False);
}




final simulated function DoFragments()
{
    local gDestroyerNode Node, NextNode, PrevNode;
    local gDestroyerNode.EBeamCollision NewCollision, LastCollision;
    local SBeamFragment BeamFragment;
    local int i;

    BeamFragments.Length = 0;
    LastCollision = BC_None;

    Node = NodeFirst;
    while( Node != None )
    {
        NextNode = Node.Next;
        PrevNode = Node.Prev;

        for( i=0; i<Node.BeamPoints.Length-1 ; ++i )
        {
            NewCollision = Node.BeamCollision[i];

            if( NewCollision == BC_None )
            {
                if( LastCollision == BC_None )
                {
                    // add
                    BeamFragment.Points[BeamFragment.Points.Length] = Node.BeamPoints[i];
                }
                else
                {
                    // new
                    BeamFragment.Points.Length = 0;
                    BeamFragment.Points[BeamFragment.Points.Length] = Node.BeamPoints[i];
                }
            }
            else
            {
                if( LastCollision == BC_None )
                {
                    // stop
                    if( BeamFragment.Points.Length > 1 )
                    {
                        BeamFragments[BeamFragments.Length] = BeamFragment;
                    }
                    BeamFragment.Points.Length = 0;
                }
                else
                {
                    // ignore
                }
            }


            LastCollision = NewCollision;
        }

        Node = NextNode;
    }

    if( LastCollision == BC_None && BeamFragment.Points.Length > 1 )
    {
        BeamFragments[BeamFragments.Length] = BeamFragment;
    }
}




final simulated function DoFireDamage(Actor A)
{
    local gDestroyerDOT D;
    local int i;

    for( i = 0; i != A.Attached.Length; ++i )
    {
        D = gDestroyerDOT(A.Attached[i]);
        if( D != None )
        {
            D.DelayedDamage(Instigator);
            return;
        }
    }

    Spawn(class'gDestroyerDOT',A,,A.Location,A.Rotation);
}


final simulated function DoBeamPoints()
{
    local gDestroyerNode Node, NextNode, PrevNode;
    local vector StartLoc, StartControl, EndLoc, EndControl, PrevLoc, NewLoc;
    local float BezierAlpha, TessDelta;
    local int i;

    if( NodeFirst == None || NodeFirst.Next == None )
        return;

    TessDelta = 1.0 / float(Tesselation);

    NodeFirst.BeamPoints.Length = 0;
    NodeFirst.BeamPoints[NodeFirst.BeamPoints.Length] = NodeFirst.Location;
    NodeLast.BeamPoints.Length = 0;
    NodeLast.BeamPoints[NodeLast.BeamPoints.Length] = NodeLast.Location;

    Node = NodeFirst.Next;
    while( Node != None )
    {
        NextNode = Node.Next;
        PrevNode = Node.Prev;

        PrevNode.BeamPoints.Length = 0;

        StartLoc = PrevNode.Location;
        StartControl = PrevNode.Location - PrevNode.Control;
        EndLoc = Node.Location;
        EndControl = Node.Location + Node.Control;


        PrevNode.BeamPoints[PrevNode.BeamPoints.Length] = StartLoc;

        PrevLoc = StartLoc;
        BezierAlpha = TessDelta;

        for( i=0; i<Tesselation; ++i )
        {
            NewLoc = Bezier( BezierAlpha, StartLoc, StartControl, EndLoc, EndControl );

            PrevNode.BeamPoints[PrevNode.BeamPoints.Length] = NewLoc;

            BezierAlpha += TessDelta;
            PrevLoc = NewLoc;
        }

        Node = NextNode;
    }

}

final simulated function DoOldBeamPoints()
{
    local gDestroyerNode Node, NextNode, PrevNode;
    local int i;


    Node = NodeFirst;
    while( Node != None )
    {
        NextNode = Node.Next;
        PrevNode = Node.Prev;

        Node.OldBeamPoints.Length = Node.BeamPoints.Length;

        for( i=0; i<Node.BeamPoints.Length ; ++i )
        {
            Node.OldBeamPoints[i] = Node.BeamPoints[i];
        }

        Node = NextNode;
    }
}

final simulated function DoControlPoints( float DeltaTime)
{
    local gDestroyerNode Node, NextNode, PrevNode;

    Node = NodeFirst;

    while( Node != None )
    {
        NextNode = Node.Next;
        PrevNode = Node.Prev;

        CalcControlPoint(Node,NextNode);

        Node = NextNode;
    }

    NodeFirst.Control = vect(0,0,0);
    NodeLast.Control = vect(0,0,0);

    // anchor control point
    if( NodeFirst.bAnchor && NodeFirst.Next != None )
    {
        NodeFirst.Control = -NodeFirst.Dir * VSize(NodeFirst.Next.Location-NodeFirst.Location) * ControlDistAnchor;
    }
}

final simulated function CalcControlPoint( gDestroyerNode Node, gDestroyerNode NextNode )
{
    local gDestroyerNode NextNextNode;

    // Use old settings on terminal nodes
    //if( Node.bTruncate || (NextNode != None && NextNode.bTerminal) )
    //    return;

    // calc bezier control points
    // last node and anchor calculated outside of the loop
    if( NextNode != None )
    {
        // find vector to newer node
        NextNode.Delta = Node.Location - NextNode.Location;
        NextNode.Dist = VSize(NextNode.Delta);

        // auto-calc control point
        NextNextNode = NextNode.Next;
        if( NextNextNode != None )
        {
            if( NextNode.Dist < NextNextNode.Dist )
            {
                NextNode.Control = Normal(Node.Location - NextNextNode.Location) * NextNode.Dist * ControlDistNode;
            }
            else
            {
                NextNode.Control = Normal(Node.Location - NextNextNode.Location) * NextNextNode.Dist * ControlDistNode;
            }
        }
        else
        {
        }
    }
    else
    {
    }
}


final simulated function vector Bezier( float Alpha, vector StartLoc, vector StartControl, vector EndLoc, vector EndControl )
{
    local vector ab,bc,cd,abbc,bccd,dest;

    ab = StartLoc+(StartControl-StartLoc)*Alpha;
    bc = StartControl+(EndControl-StartControl)*Alpha;
    cd = EndControl+(EndLoc-EndControl)*Alpha;

    abbc = ab+(bc-ab)*Alpha;
    bccd = bc+(cd-bc)*Alpha;
    dest = abbc+(bccd-abbc)*Alpha;

    return dest;
}


// ============================================================================
//  Draw
// ============================================================================

final simulated function DoEmitterSpawning()
{
    local int i;

    if( BeamEmitters.Length < BeamFragments.Length )
    {
        for( i=BeamEmitters.Length; i<BeamFragments.Length; ++i )
        {
            BeamEmitters[i] = Spawn(BeamEmitterClass);
        }
    }
    else
    {
        for( i=BeamFragments.Length; i<BeamEmitters.Length; ++i )
        {
            if( BeamEmitters[i] != None )
            {
                BeamEmitters[i].Destroy();
            }
        }
        BeamEmitters.Length = BeamFragments.Length;
    }
    assert( BeamEmitters.Length == BeamFragments.Length);


    if( DispEmitters.Length < BeamFragments.Length*2 )
    {
        for( i=DispEmitters.Length; i<BeamFragments.Length*2; ++i )
        {
            DispEmitters[i] = Spawn(DispEmitterClass);
        }
    }
    else
    {
        for( i=BeamFragments.Length*2; i<DispEmitters.Length; ++i )
        {
            if( DispEmitters[i] != None )
            {
                DispEmitters[i].Destroy();
            }
        }
        DispEmitters.Length = BeamFragments.Length*2;
    }
    assert( DispEmitters.Length == BeamFragments.Length*2);
}

final simulated function DrawBeam()
{
    local vector PrevLoc, NewLoc;
    local int i, j, NodeIdx, MaxIdx;
    local BeamEmitter PE;
    local SBeamFragment BeamFragment;
    local Emitter BeamEmitter;
    local float BeamLength, LengthMult;

    if( NodeFirst == None || NodeFirst.Next == None )
    {
        return;
    }

    for( i=0; i<BeamFragments.Length; ++i )
    {
        BeamFragment = BeamFragments[i];
        BeamEmitter = BeamEmitters[i];
        PE = BeamEmitter(BeamEmitter.Emitters[0]);
        MaxIdx = PE.HFPoints.Length;
        NodeIdx = 0;
        BeamLength = 0;

        if( BeamFragment.Points.Length > 0 )
        {
            DispEmitters[i*2].SetLocation(BeamFragment.Points[0]);
            PrevLoc = BeamFragment.Points[0];
        }

        for( j=0; j<BeamFragment.Points.Length; ++j )
        {
            if( NodeIdx<MaxIdx )
            {
                NewLoc = BeamFragment.Points[j];
                BeamLength += VSize(NewLoc-PrevLoc);
                PE.HFPoints[NodeIdx++].Location = NewLoc;
                PrevLoc = NewLoc;
            }
            else
            {
                break;
            }
        }

        // move excess points to last beam point
        for( j=NodeIdx; j<MaxIdx; ++j )
        {
            PE.HFPoints[j].Location = PrevLoc;
        }

        DispEmitters[(i*2)+1].SetLocation(PrevLoc);
        DispEmitters[(i*2)+1].bHidden = False;

        LengthMult = (float(NodeIdx)/float(MaxIdx));
        PE.BeamTextureUScale = (BeamLength/LengthMult) / 256.0;
    }

    if( NodeFirst.bAnchor )
    {
        if( DispEmitters.Length > 0 )
        {
            DispEmitters[0].bHidden = True;
        }

        if( FlashEffect == None )
        {
            FlashEffect = Spawn(FlashEffectClass);
        }

        if( FlashEffect != None )
        {
            FlashEffect.SetLocation(NodeFirst.Location);
            FlashEffect.SetRotation(rotator(NodeFirst.Next.Location - NodeFirst.Location));
        }

        /* TODO: this would require recalculating the first fragment
        if( BeamEmitters.Length > 0 )
        {
            PE = BeamEmitter(BeamEmitters[0].Emitters[0]);
            if( Pawn(Owner).IsFirstPerson() && Pawn(Owner).Weapon != None )
            {
                PE.HFPoints[0].Location = Pawn(Owner).Weapon.GetBoneCoords('Muzzle').Origin;
                //log("1");
            }
            else if( xPawn(Owner).WeaponAttachment != None )
            {
                PE.HFPoints[0].Location = xPawn(Owner).WeaponAttachment.GetBoneCoords('Muzzle').Origin;
                //log("3");
            }
            else
            {
                //log(self);
            }
        }*/
    }
    else
    {
    }
}

// ============================================================================
//  DefaultProperties
// ============================================================================

defaultproperties
{
     Speed=4500.000000
     LifeDist=3200.000000
     Tesselation=10
     ControlDistNode=0.330000
     ControlDistAnchor=0.750000
     FixedTickDelta=0.050000
     bDrawBeam=True
     bDrawDebugNodes=True
     bDrawDebugNodeCross=True
     bDrawDebugBeam=True
     ColorTruncate=(B=255,R=255)
     ColorNode=(B=255)
     ColorNode2=(G=255)
     ColorSegment=(R=255)
     ColorSegment2=(G=255,R=255)
     DebugCrossSize=32.000000
     ScorchClass=Class'Taskforces.gDestroyerBeamScorch'
     MinScorchDist=16.000000
     HitEffectClass=Class'Taskforces.gDestroyerBeamHit'
     MinHitDist=64.000000
     BeamEmitterClass=Class'Taskforces.gDestroyerBeamEmitter'
     DispEmitterClass=Class'Taskforces.gDestroyerDispEmitter'
     FlashEffectClass=Class'Taskforces.gDestroyerAltMuzzle'
     DrawType=DT_StaticMesh
     bAcceptsProjectors=False
     bAlwaysRelevant=True
     bReplicateInstigator=True
     bUpdateSimulatedPosition=True
     bNetInitialRotation=True
     RemoteRole=ROLE_SimulatedProxy
     NetPriority=4.000000
}
